Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.97% covered (success)
94.97%
434 / 457
58.82% covered (warning)
58.82%
10 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Taxonomy
94.97% covered (success)
94.97%
434 / 457
58.82% covered (warning)
58.82%
10 / 17
50.32
0.00% covered (danger)
0.00%
0 / 1
 init
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
3.19
 hooks
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registerTaxonomies
100.00% covered (success)
100.00%
96 / 96
100.00% covered (success)
100.00%
1 / 1
2
 insertTerms
99.60% covered (success)
99.60%
247 / 248
0.00% covered (danger)
0.00%
0 / 1
2
 insertLicenseTerms
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getFrontMatterType
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getBackMatterType
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getChapterType
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 getGlossaryType
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
5.02
 maybeUpgrade
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 upgrade
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 upgradeLicenses
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 differentiatePublicDomain
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 upgradeChapterTypes
50.00% covered (danger)
50.00%
10 / 20
0.00% covered (danger)
0.00%
0 / 1
10.50
 upgradeToContributorTaxonomy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeTaxonomyViewLinks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * This class has two purposes:
4 *  + Handle the registration and population of taxonomies.
5 *  + Perform upgrades on individual books as Pressbooks evolves.
6 *
7 * @author  Pressbooks <code@pressbooks.com>
8 * @license GPLv3 (or any later version)
9 */
10
11namespace Pressbooks;
12
13class Taxonomy {
14
15    /**
16     * The value for option: pressbooks_taxonomy_version
17     *
18     * @see upgrade()
19     * @var int
20     */
21    const VERSION = 4;
22
23    /**
24     * @var Taxonomy
25     */
26    private static $instance = null;
27
28    /**
29     * @var Licensing
30     */
31    private $licensing;
32
33    /**
34     * @var Contributors
35     */
36    private $contributors;
37
38    /**
39     * @return Taxonomy
40     */
41    static public function init() {
42        if ( is_null( self::$instance ) ) {
43            $licensing = new Licensing();
44            $contributor = new Contributors();
45            self::$instance = new self( $licensing, $contributor );
46            self::hooks( self::$instance );
47        }
48        return self::$instance;
49    }
50
51    /**
52     * @param Taxonomy $obj
53     */
54    public static function hooks( Taxonomy $obj ) {
55        if ( Book::isBook() ) {
56            add_action( 'init', [ $obj, 'maybeUpgrade' ], 1000 );
57            add_action( 'user_register', [ $obj->contributors, 'addBlogUser' ] );
58            add_action( 'add_user_to_blog', [ $obj, 'registerTaxonomies' ] ); // This is a workaround because newbloguser does not fire init action.
59            add_action( 'add_user_to_blog', [ $obj->contributors, 'addBlogUser' ] );
60            add_action( 'set_user_role', [ $obj->contributors, 'addBlogUser' ] );
61            add_action( 'profile_update', [ $obj->contributors, 'updateBlogUser' ], 10, 2 );
62            add_action( 'added_post_meta', [ $obj, 'upgradeToContributorTaxonomy' ], 10, 4 );
63            add_action( 'updated_postmeta', [ $obj, 'upgradeToContributorTaxonomy' ], 10, 4 );
64            add_filter( 'front-matter-type_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
65            add_filter( 'back-matter-type_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
66            add_filter( 'chapter-type_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
67            add_filter( 'glossary-type_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
68            add_filter( 'license_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
69            add_filter( 'contributor_row_actions', [ $obj, 'removeTaxonomyViewLinks' ], 10, 2 );
70        }
71        add_action( 'init', [ $obj, 'registerTaxonomies' ] );
72    }
73
74    /**
75     * @param Licensing $licensing
76     * @param Contributors $contributors
77     */
78    public function __construct( $licensing, $contributors ) {
79        $this->licensing = $licensing;
80        $this->contributors = $contributors;
81    }
82
83    /**
84     * Register taxonomies
85     */
86    public function registerTaxonomies() {
87        register_extended_taxonomy(
88            'front-matter-type',
89            'front-matter',
90            [
91                'meta_box' => 'dropdown',
92                'meta_box_sanitize_cb' => 'taxonomy_meta_box_sanitize_cb_input',
93                'capabilities' => [
94                    'manage_terms' => 'manage_sites',
95                    'edit_terms' => 'manage_sites',
96                    'delete_terms' => 'manage_sites',
97                    'assign_terms' => 'edit_posts',
98                ],
99                'show_in_rest' => true,
100            ]
101        );
102
103        register_extended_taxonomy(
104            'back-matter-type',
105            'back-matter',
106            [
107                'meta_box' => 'dropdown',
108                'meta_box_sanitize_cb' => 'taxonomy_meta_box_sanitize_cb_input',
109                'capabilities' => [
110                    'manage_terms' => 'manage_sites',
111                    'edit_terms' => 'manage_sites',
112                    'delete_terms' => 'manage_sites',
113                    'assign_terms' => 'edit_posts',
114                ],
115                'show_in_rest' => true,
116            ]
117        );
118
119        register_extended_taxonomy(
120            'chapter-type',
121            'chapter',
122            [
123                'meta_box' => 'dropdown',
124                'meta_box_sanitize_cb' => 'taxonomy_meta_box_sanitize_cb_input',
125                'capabilities' => [
126                    'manage_terms' => 'manage_sites',
127                    'edit_terms' => 'manage_sites',
128                    'delete_terms' => 'manage_sites',
129                    'assign_terms' => 'edit_posts',
130                ],
131                'show_in_rest' => true,
132            ]
133        );
134
135        register_extended_taxonomy(
136            'contributor',
137            [ 'metadata', 'chapter', 'part', 'front-matter', 'back-matter' ],
138            [
139                'meta_box' => false,
140                'hierarchical' => false,
141                'capabilities' => [
142                    'manage_terms' => 'manage_options',
143                    'edit_terms' => 'manage_options',
144                    'delete_terms' => 'manage_options',
145                    'assign_terms' => 'edit_posts',
146                ],
147                'show_in_rest' => true,
148            ]
149        );
150
151        register_extended_taxonomy(
152            'license',
153            [ 'metadata', 'chapter', 'part', 'front-matter', 'back-matter' ],
154            [
155                'meta_box' => false,
156                'hierarchical' => false,
157                'capabilities' => [
158                    'manage_terms' => 'manage_sites',
159                    'edit_terms' => 'manage_sites',
160                    'delete_terms' => 'manage_sites',
161                    'assign_terms' => 'edit_posts',
162                ],
163                'show_in_rest' => true,
164            ]
165        );
166
167        register_extended_taxonomy(
168            'glossary-type',
169            'glossary',
170            [
171                'meta_box' => 'dropdown',
172                'meta_box_sanitize_cb' => 'taxonomy_meta_box_sanitize_cb_input',
173                'capabilities' => [
174                    'manage_terms' => 'manage_sites',
175                    'edit_terms' => 'manage_sites',
176                    'delete_terms' => 'manage_sites',
177                    'assign_terms' => 'edit_posts',
178                ],
179                'show_in_rest' => true,
180            ]
181        );
182        if ( ! get_term_by( 'slug', 'contributors', 'back-matter-type' ) ) {
183            wp_insert_term(
184                'Contributors', 'back-matter-type', [
185                    'slug' => 'contributors',
186                ]
187            );
188        }
189    }
190
191    /**
192     * Insert terms
193     *
194     * If the term already exists on the same hierarchical level, or the term slug and name are not unique,
195     * wp_insert_term() returns a WP_Error and we ignore it.
196     */
197    public function insertTerms() {
198
199        if ( ! taxonomy_exists( 'front-matter-type' ) ) {
200            $this->registerTaxonomies();
201        }
202
203        // Front Matter
204        wp_insert_term(
205            'Abstract', 'front-matter-type', [
206                'slug' => 'abstracts',
207            ]
208        );
209        wp_insert_term(
210            'Acknowledgements', 'front-matter-type', [
211                'slug' => 'acknowledgements',
212            ]
213        );
214        wp_insert_term(
215            'Before Title Page', 'front-matter-type', [
216                'slug' => 'before-title',
217            ]
218        );
219        wp_insert_term(
220            'Chronology, Timeline', 'front-matter-type', [
221                'slug' => 'chronology-timeline',
222            ]
223        );
224        wp_insert_term(
225            'Dedication', 'front-matter-type', [
226                'slug' => 'dedication',
227            ]
228        );
229        wp_insert_term(
230            'Disclaimer', 'front-matter-type', [
231                'slug' => 'disclaimer',
232            ]
233        );
234        wp_insert_term(
235            'Epigraph', 'front-matter-type', [
236                'slug' => 'epigraph',
237            ]
238        );
239        wp_insert_term(
240            'Foreword', 'front-matter-type', [
241                'slug' => 'foreword',
242            ]
243        );
244        wp_insert_term(
245            'Genealogy, Family Tree', 'front-matter-type', [
246                'slug' => 'genealogy-family-tree',
247            ]
248        );
249        wp_insert_term(
250            'Image credits', 'front-matter-type', [
251                'slug' => 'image-credits',
252            ]
253        );
254        wp_insert_term(
255            'Introduction', 'front-matter-type', [
256                'slug' => 'introduction',
257            ]
258        );
259        wp_insert_term(
260            'List of Abbreviations', 'front-matter-type', [
261                'slug' => 'list-of-abbreviations',
262            ]
263        );
264        wp_insert_term(
265            'List of Characters', 'front-matter-type', [
266                'slug' => 'list-of-characters',
267            ]
268        );
269        wp_insert_term(
270            'List of Illustrations', 'front-matter-type', [
271                'slug' => 'list-of-illustrations',
272            ]
273        );
274        wp_insert_term(
275            'List of Tables', 'front-matter-type', [
276                'slug' => 'list-of-tables',
277            ]
278        );
279        wp_insert_term(
280            'Miscellaneous', 'front-matter-type', [
281                'slug' => 'miscellaneous',
282            ]
283        );
284        wp_insert_term(
285            'Other Books by Author', 'front-matter-type', [
286                'slug' => 'other-books',
287            ]
288        );
289        wp_insert_term(
290            'Preface', 'front-matter-type', [
291                'slug' => 'preface',
292            ]
293        );
294        wp_insert_term(
295            'Prologue', 'front-matter-type', [
296                'slug' => 'prologue',
297            ]
298        );
299        wp_insert_term(
300            'Recommended citation', 'front-matter-type', [
301                'slug' => 'recommended-citation',
302            ]
303        );
304        wp_insert_term(
305            'Title Page', 'front-matter-type', [
306                'slug' => 'title-page',
307            ]
308        );
309
310        // Back Matter
311        wp_insert_term(
312            'About the Author', 'back-matter-type', [
313                'slug' => 'about-the-author',
314            ]
315        );
316        wp_insert_term(
317            'About the Publisher', 'back-matter-type', [
318                'slug' => 'about-the-publisher',
319            ]
320        );
321        wp_insert_term(
322            'Acknowledgements', 'back-matter-type', [
323                'slug' => 'acknowledgements',
324            ]
325        );
326        wp_insert_term(
327            'Afterword', 'back-matter-type', [
328                'slug' => 'afterword',
329            ]
330        );
331        wp_insert_term(
332            'Appendix', 'back-matter-type', [
333                'slug' => 'appendix',
334            ]
335        );
336        wp_insert_term(
337            "Author's Note", 'back-matter-type', [
338                'slug' => 'authors-note',
339            ]
340        );
341        wp_insert_term(
342            'Back of Book Ad', 'back-matter-type', [
343                'slug' => 'back-of-book-ad',
344            ]
345        );
346        wp_insert_term(
347            'Bibliography', 'back-matter-type', [
348                'slug' => 'bibliography',
349            ]
350        );
351        wp_insert_term(
352            'Biographical Note', 'back-matter-type', [
353                'slug' => 'biographical-note',
354            ]
355        );
356        wp_insert_term(
357            'Colophon', 'back-matter-type', [
358                'slug' => 'colophon',
359            ]
360        );
361        wp_insert_term(
362            'Conclusion', 'back-matter-type', [
363                'slug' => 'conclusion',
364            ]
365        );
366        wp_insert_term(
367            'Contributors', 'back-matter-type', [
368                'slug' => 'contributors',
369            ]
370        );
371        wp_insert_term(
372            'Credits', 'back-matter-type', [
373                'slug' => 'credits',
374            ]
375        );
376        wp_insert_term(
377            'Dedication', 'back-matter-type', [
378                'slug' => 'dedication',
379            ]
380        );
381        wp_insert_term(
382            'Epilogue', 'back-matter-type', [
383                'slug' => 'epilogue',
384            ]
385        );
386        wp_insert_term(
387            'Glossary', 'back-matter-type', [
388                'slug' => 'glossary',
389            ]
390        );
391        wp_insert_term(
392            'Index', 'back-matter-type', [
393                'slug' => 'index',
394            ]
395        );
396        wp_insert_term(
397            'Miscellaneous', 'back-matter-type', [
398                'slug' => 'miscellaneous',
399            ]
400        );
401        wp_insert_term(
402            'Notes', 'back-matter-type', [
403                'slug' => 'notes',
404            ]
405        );
406        wp_insert_term(
407            'Other Books by Author', 'back-matter-type', [
408                'slug' => 'other-books',
409            ]
410        );
411        wp_insert_term(
412            'Permissions', 'back-matter-type', [
413                'slug' => 'permissions',
414            ]
415        );
416        wp_insert_term(
417            'Reading Group Guide', 'back-matter-type', [
418                'slug' => 'reading-group-guide',
419            ]
420        );
421        wp_insert_term(
422            'Resources', 'back-matter-type', [
423                'slug' => 'resources',
424            ]
425        );
426        wp_insert_term(
427            'Sources', 'back-matter-type', [
428                'slug' => 'sources',
429            ]
430        );
431        wp_insert_term(
432            'Suggested Reading', 'back-matter-type', [
433                'slug' => 'suggested-reading',
434            ]
435        );
436
437        // Chapter
438        wp_insert_term(
439            'Standard', 'chapter-type', [
440                'slug' => 'standard',
441            ]
442        );
443        wp_insert_term(
444            'Numberless', 'chapter-type', [
445                'slug' => 'numberless',
446            ]
447        );
448
449        // Glossary
450        wp_insert_term(
451            'Miscellaneous', 'glossary-type', [
452                'slug' => 'miscellaneous',
453            ]
454        );
455
456        $this->insertLicenseTerms();
457    }
458
459    public function insertLicenseTerms() {
460        $extended = apply_filters( 'extend_custom_licenses', [] ); // override inserted license terms only if this hook is called
461        $supported_licenses = $this->licensing->getSupportedTypes( true, true );
462        $licenses = ( count( $extended ) > 0 ) ? array_merge( $supported_licenses, $extended ) : $supported_licenses;
463        foreach ( $licenses as $key => $val ) {
464                wp_insert_term(
465                    $val['desc'], Licensing::TAXONOMY, [
466                        'slug' => $key,
467                    ]
468                );
469        }
470    }
471
472    /**
473     * Return the first (and only) front-matter-type for a specific post
474     *
475     * @param int $id Post ID
476     *
477     * @return string
478     */
479    public function getFrontMatterType( $id ) {
480
481        $terms = get_the_terms( $id, 'front-matter-type' );
482        if ( $terms && ! is_wp_error( $terms ) ) {
483            foreach ( $terms as $term ) {
484                return $term->slug;
485            }
486        }
487
488        return 'miscellaneous';
489    }
490
491    /**
492     * Return the first (and only) back-matter-type for a specific post
493     *
494     * @param int $id Post ID
495     *
496     * @return string
497     */
498    public function getBackMatterType( $id ) {
499
500        $terms = get_the_terms( $id, 'back-matter-type' );
501        if ( $terms && ! is_wp_error( $terms ) ) {
502            foreach ( $terms as $term ) {
503                return $term->slug;
504            }
505        }
506
507        return 'miscellaneous';
508    }
509
510    /**
511     * Return the first (and only) chapter-type for a specific post
512     *
513     * @param int $id Post ID
514     *
515     * @return string
516     */
517    public function getChapterType( $id ) {
518
519        $terms = get_the_terms( $id, 'chapter-type' );
520        if ( $terms && ! is_wp_error( $terms ) ) {
521            foreach ( $terms as $term ) {
522                if ( 'type-1' === $term->slug ) {
523                    return 'standard';
524                } else {
525                    return $term->slug;
526                }
527            }
528        }
529
530        return 'standard';
531    }
532
533    /**
534     * Return the first (and only) glossary-type for a specific post
535     *
536     * @param $id
537     *
538     * @return string
539     */
540    public function getGlossaryType( $id ) {
541
542        $terms = get_the_terms( $id, 'glossary-type' );
543        if ( $terms && ! is_wp_error( $terms ) ) {
544            foreach ( $terms as $term ) {
545                return $term->slug;
546            }
547        }
548
549        return 'miscellaneous';
550    }
551
552    // ----------------------------------------------------------------------------------------------------------------
553    // Upgrades
554    // ----------------------------------------------------------------------------------------------------------------
555
556    /**
557     * Is it time to upgrade?
558     */
559    public function maybeUpgrade() {
560        $taxonomy_version = get_option( 'pressbooks_taxonomy_version', 0 );
561        if ( $taxonomy_version < self::VERSION ) {
562            $this->upgrade( $taxonomy_version );
563            update_option( 'pressbooks_taxonomy_version', self::VERSION );
564        }
565    }
566
567    /**
568     * Upgrade
569     *
570     * @param int $version
571     */
572    public function upgrade( $version ) {
573
574        if ( $version < 1 ) {
575            // Upgrade from version 0 (prior to Pressbooks\Taxonomy class) to version 1 (simplified chapter types)
576            $this->upgradeChapterTypes();
577            flush_rewrite_rules( false );
578        }
579        if ( $version < 2 ) {
580            $this->insertTerms(); // Re-trigger
581        }
582        if ( $version < 3 ) {
583            $this->upgradeLicenses();
584        }
585        if ( $version < 4 ) {
586            $this->differentiatePublicDomain();
587        }
588    }
589
590    /**
591     *
592     */
593    protected function upgradeLicenses() {
594        global $wpdb;
595        $results = $wpdb->get_results(
596            $wpdb->prepare(
597                "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key IN (%s, %s)",
598                [ 'pb_section_license', 'pb_book_license' ]
599            ), ARRAY_A
600        );
601        foreach ( $results as $val ) {
602            wp_set_object_terms( $val['post_id'], $val['meta_value'], Licensing::TAXONOMY );
603        }
604    }
605
606    /**
607     *
608     */
609    protected function differentiatePublicDomain() {
610        foreach ( $this->licensing->getSupportedTypes( true, true ) as $key => $val ) {
611            if ( $key === 'public-domain' ) {
612                $public_domain = get_term_by( 'slug', $key, Licensing::TAXONOMY );
613                wp_update_term(
614                    $public_domain->term_id, Licensing::TAXONOMY, [
615                        'name' => $val['desc'],
616                    ]
617                );
618            }
619            if ( $key === 'cc-zero' ) {
620                wp_insert_term(
621                    $val['desc'], Licensing::TAXONOMY, [
622                        'slug' => $key,
623                    ]
624                );
625            }
626        }
627    }
628
629    /**
630     * Upgrade Chapter Types.
631     */
632    protected function upgradeChapterTypes() {
633        $type_1 = get_term_by( 'slug', 'type-1', 'chapter-type' );
634        $type_2 = get_term_by( 'slug', 'type-2', 'chapter-type' );
635        $type_3 = get_term_by( 'slug', 'type-3', 'chapter-type' );
636        $type_4 = get_term_by( 'slug', 'type-4', 'chapter-type' );
637        $type_5 = get_term_by( 'slug', 'type-5', 'chapter-type' );
638
639        if ( $type_1 ) {
640            wp_update_term(
641                $type_1->term_id, 'chapter-type', [
642                    'name' => 'Standard',
643                    'slug' => 'standard',
644                ]
645            );
646        }
647
648        if ( $type_2 ) {
649            wp_delete_term( $type_2->term_id, 'chapter-type' );
650        }
651
652        if ( $type_3 ) {
653            wp_delete_term( $type_3->term_id, 'chapter-type' );
654        }
655
656        if ( $type_4 ) {
657            wp_delete_term( $type_4->term_id, 'chapter-type' );
658        }
659
660        if ( $type_5 ) {
661            wp_delete_term( $type_5->term_id, 'chapter-type' );
662        }
663    }
664
665    /**
666     * If some plugin is still saving to the old/deprecated contributor slugs, then upgrade to Pressbooks Five Data Model
667     *
668     * @since 5.0.0
669     *
670     * @param int $meta_id ID of updated metadata entry.
671     * @param int $object_id Post ID.
672     * @param string $meta_key Meta key.
673     * @param mixed $meta_value Meta value.
674     *
675     * @return array|false An array containing the `term_id` and `term_taxonomy_id`, false otherwise.
676     */
677    public function upgradeToContributorTaxonomy( $meta_id, $object_id, $meta_key, $meta_value ) {
678        return $this->contributors->convert( $meta_key, $meta_value, $object_id );
679    }
680
681    /**
682     * Remove the "View" link from the taxonomies.
683     *
684     * @param array $actions The default actions.
685     * @param \WP_Term $tag The term object.
686     * @return array
687     */
688    public function removeTaxonomyViewLinks( $actions, $tag ) {
689        unset( $actions['view'] );
690        return $actions;
691    }
692}